/**
 * gun.gchart.js
 *
 * changes: 0.3 (2008-10-03)
 * - add venn diagram
 * - add bar chart ( not complete yet. )
 * - add legends ( super-class )
 *
 * @author http://www.yangkun.pe.kr
 * @version 0.3
 * @license MIT-style license.
 */
if (!$type(gun)) var gun = {};
if (!$type(gun.gchart)) {
	gun.gchart = {};

	gun.gchart.BOTTOM = 'b';
	gun.gchart.TOP = 't';
	gun.gchart.RIGHT = 'r';
	gun.gchart.LEFT = 'l';

	gun.gchart.ENCODE_TEXT = 't';
	gun.gchart.ENCODE_SIMPLE = 's';
	gun.gchart.ENCODE_EXTEND = 'e';

	gun.gchart.eEncodeValues = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
	gun.gchart.eEncodeMod = (gun.gchart.eEncodeValues.length * gun.gchart.eEncodeValues.length) - 1;

	gun.gchart.extendEncode = function(datas, maxValue) {
		var encoded = [];
		for (var i = 0, n = datas.length; i < n; i++) {
			var d = datas[i];
			if (!isNaN(d) && d >= 0) {
				var scaled = Math.round((gun.gchart.eEncodeMod * d) / maxValue);
				encoded.push(gun.gchart.eEncodeValues.charAt((scaled/gun.gchart.eEncodeValues.length).toInt()) + gun.gchart.eEncodeValues.charAt(scaled % gun.gchart.eEncodeValues.length));
			} else {
				encoded.push('__');
			}
		}
		return encoded.join('');
	};

	gun.gchart.sEncodeValues = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
	gun.gchart.simpleEncode = function(datas, maxValue) {
		var encoded = [];
		for (var i = 0, n = datas.length; i < n; i++) {
			var d = datas[i];
			if (!isNaN(d) && d >= 0) {
				encoded.push( gun.gchart.sEncodeValues.charAt( Math.round( (gun.gchart.sEncodeValues.length - 1) * d / maxValue )));
			} else {
				encoded.push('_');
			}
		}
		return encoded.join('');
	};
	/**
	* class: Data
	*/
	gun.gchart.Data = new Class({
		initialize: function(data, color, legend) {
			this.setData(data);
			this.setColor(color);
			this.setLegend(legend);
		},
		setData:function(data) {
			this.data = [];
			if ($type(data) == 'string') {
				this.data = data.split(',');
				if (this.data.length == 1) this.data = data.split('|');
			} else if ($type(data) == 'array') {
				this.data = data;
			}
		},
		setColor: function(color) {
			this.color = color;
		},
		setLegend: function(legend) {
			this.legend = legend;
		}
	});
	/**
	* class: DataSet
	*/
	gun.gchart.DataSet = new Class({

		encode: gun.gchart.ENCODE_EXTEND,
		datas: [],

		initialize: function(encode) {
			this.encode = encode;
		},
		addData: function(data) {
			this.datas.push(data);
		},
		getMaxValue: function() {
			var max = 0;
			for (var i = 0; i < this.datas.length; i++) {
				for (var j = 0,k = this.datas[i].data.length; j < k; j++) {
					max = Math.max(max, this.datas[i].data[j]);
				}
			}
			return max;
		},
		getColors: function() {
			var colors = [];
			this.datas.each(function(d){ if($chk(d.color)) colors.push(d.color); });
			return (colors.length > 0) ? colors : null;
		},
		getLegends: function() {
			var legends = [];
			this.datas.each(function(d){ if($chk(d.legend)) legends.push(d.legend); });
			return (legends.length > 0) ? legends : null;
		},
		getDataString: function() {
			var dataString = this.encode + ':';
			var datas = [];
			if (this.encode == gun.gchart.ENCODE_EXTEND) {
				var maxValue = this.getMaxValue();
				for (var i = 0; i < this.datas.length; i++) datas.push(gun.gchart.extendEncode(this.datas[i].data, maxValue));
				dataString += datas.join(',');
			} else if (this.encode == gun.gchart.ENCODE_SIMPLE) {
				var maxValue = this.getMaxValue();
				for (var i = 0; i < this.datas.length; i++) datas.push(gun.gchart.simpleEncode(this.datas[i].data, maxValue));
				dataString += datas.join(',');
			} else if (this.encode == gun.gchart.ENCODE_TEXT) {
				for (var i = 0; i < this.datas.length; i++) datas.push(this.datas[i].data.join(','));
				dataString += datas.join('|');
			}
			return dataString;
		}
	});
	/**
	* class: Theme
	*/
	gun.gchart.Theme = new Class({
		initialize: function() {
			this.chartColors = null;

			this.titleSize = 20;
			this.titleColor = '000000';

			this.bgFillType = 'solid';
			this.bgFill = 'ffffff';
			this.bgAngle = null;
			this.bgValues = null;

			this.chartFillType = 'solid';
			this.chartFill = 'ffffff';
			this.chartAngle = null;
			this.chartValues = null;

			this.legendPosition = gun.gchart.RIGHT;
		},
		setChartColors: function(colors) {
			if ($type(colors) == 'array') this.chartColors = colors.join('|');
			else this.chartColors = colors;
		},
		setTitle: function(size,color) {
			this.titleSize = size;
			this.titleColor = color;
		},
		setFillBackground: function(type, color, angle, values) {
			this.bgFillType = type;
			this.bgFill = color;
			if (['gradient','gr','stripes', 'stripe','st','str'].contains(type)) {
				this.bgAngle = angle;
				this.bgValues = values;
			}
		},
		setFillChart: function(type, color, angle, values) {
			this.chartFillType = type;
			this.chartFill = color;
			if (['gradient','gr','stripes', 'stripe','st','str'].contains(type)) {
				this.chartAngle = angle;
				this.chartValues = values;
			}
		},
		setParameters: function(params) {
			params.include('chts', this.titleColor + ',' + this.titleSize);
			var fills = [];

			if (this.bgFillType == 'alpha') {
				fills.push('a,s,' + this.bgFill);
			} else {
				if (this.bgFillType == 'solid') {
					fills.push('bg,s,' + this.bgFill);
				} else if (['gradient','gr'].contains(this.bgFillType)) {
					fills.push('bg,lg,' + this.bgAngle + ',' + this.bgValues.join(','));
				} else if (['stripes', 'stripe','st','str'].contains(this.bgFillType)) {
					fills.push('bg,ls,' + this.bgAngle + ',' + this.bgValues.join(','));
				}

				if (this.chartFillType == 'solid') {
					fills.push('c,s,' + this.chartFill);
				} else if (['gradient','gr'].contains(this.chartFillType)) {
					fills.push('c,lg,' + this.chartAngle + ',' + this.chartValues.join(','));
				} else if (['stripes', 'stripe','st','str'].contains(this.chartFillType)) {
					fills.push('c,ls,' + this.chartAngle + ',' + this.chartValues.join(','));
				}
			}
			params.include('chf', fills.join('|'));
			if ($chk(this.chartColors)) params.include('chco', this.chartColors);
			if (params.keyOf('chdl')) params.include('chdlp', this.legendPosition);
		},
		setLegendPosition: function(pos) {
			this.legendPosition = pos;
		}
	});

	gun.gchart.Theme.Default = new gun.gchart.Theme();

	gun.gchart.Theme.PieDefault = new gun.gchart.Theme();
	gun.gchart.Theme.PieDefault.setChartColors('FF9900');

	/**
	* class: Basic
	*/
	gun.gchart.Basic = new Class({
		Implements: [Options],
		options: {
			url: 'http://chart.apis.google.com/chart',
			title: '',
			size: [400,200],
			data: null,
			type: null,
			image: {},
			labels: null,
			encode: gun.gchart.ENCODE_EXTEND,
			scale: null,
			theme: gun.gchart.Theme.Default
		},
		initialize: function(options) {
			this.setOptions(options);
			this.dataSet = new gun.gchart.DataSet( this.options.encode );
		},
		addData: function(data, color, legend) {
			this.dataSet.addData( new gun.gchart.Data(data, color, legend) );
		},
		setLabels: function(lbls) {
			if ($type(lbls) == 'array') {
				this.options.labels = lbls.join('|');
			} else if ($type(lbls) == 'string') {
				var arrLbl = lbls.split(',');
				if (arrLbl.length > 1) {
					this.options.labels = arrLbl.join('|');
				}
			}
		},
		setSize: function(width, height) {this.options.size = [width,height];},
		setTitle: function(title) {this.options.title = title;},
		setTheme: function(theme) {this.options.theme = theme;},
		theme: function() { return this.options.theme; },
		makeChart: function() {
			this.imageSource = this.options.url + '?' + this.buildParameters().toQueryString()
			var iOptions = {
				'src': this.imageSource,
				'width': this.options.size[0],
				'height': this.options.size[1],
				'alt': this.options.title
			};
			var image = new Element('img', $extend(iOptions, this.options.image));
			return image;
		},
		buildParameters: function() {
			var params = new Hash();
			params.include('cht', this.options.type);
			params.include('chs', this.options.size[0] + 'x' + this.options.size[1]);
			params.include('chtt', this.options.title);
			params.include('chd', this.dataSet.getDataString());
			if (this.dataSet.encode == gun.gchart.ENCODE_TEXT && $chk(this.options.scale)) params.include('chds', this.options.scale.join(','));
			if ($chk(this.options.labels)) params.include('chl', this.options.labels);
			this.onAddParameter(params);
			if ($chk(this.options.theme)) this.options.theme.setParameters(params);
			return params;
		},
		onAddParameter: function(params) { },
		inject: function(container, where) {
			this.image = this.makeChart();
			this.image.inject($(container), $pick(where, 'bottom'));
			return this.image;
		},
		draw: function() {
			this.image = this.makeChart();
			//this.image.injectInside($(document.body));
			document.write('<img src="'+this.imageSource+'" width="'+this.options.size[0]+'" height="'+this.options.size[1]+'" alt="'+this.options.title+'" class="'+this.options.image.className+'"/>');
			return this.image;
		},
		getSource: function() {
			if ($chk(this.imageSource)) return decodeURIComponent(this.imageSource);
			return 'not yet';
		}
	});
	/**
	* class: Pie
	*/
	gun.gchart.Pie = new Class({
		Extends: gun.gchart.Basic,
		options: {
			type: 'p',
			labels: null,
			theme: gun.gchart.Theme.PieDefault,
			baseColor: null
		},
		initialize: function(options) {
			this.parent(options);
			if ($chk(this.options.data)) this.addData(this.options.data);
			if ($chk(this.options.labels)) this.setLabels(this.options.labels);
			if ($chk(this.options.baseColor)) this.options.theme.setChartColors(this.options.baseColor);
			return this;
		},
		addData: function(data) {
			if ($type(data) == 'object') {
				var datas = [], labels = [];
				for (var label in data) {
					labels.push(label);
					datas.push(data[label]);
				}
				this.dataSet.addData( new gun.gchart.Data(datas) );
				this.setLabels(labels);
			} else {
				this.dataSet.addData( new gun.gchart.Data(data) );
			}
		}
	});
	/**
	* class: Pie3
	*/
	gun.gchart.Pie3 = new Class({Extends: gun.gchart.Pie,options: {type: 'p3'}});
	/**
	* class: Google-o-meter
	*/
	gun.gchart.Meter = new Class({Extends: gun.gchart.Pie,options: {type: 'gom'}});
	/**
	* class: Legends - Chart Colors
	*/
	gun.gchart.Legends = new Class({
		Extends: gun.gchart.Basic,
		hasLegend: false,
		options: {
			colors: null,
			legends: null,
			legendPosition: gun.gchart.RIGHT
		},
		initialize: function(options) {
			this.parent(options);
			if ($chk(this.options.data)) this.checkDataMatch();
			if ($chk(this.options.colors)) this.setColors( this.options.colors );
			if ($chk(this.options.legends)) this.setLegends( this.options.legends );
		},
		checkDataMatch: function() {
			if ($chk(this.options.colors) && this.options.colors.length != this.options.data.length) alert('error: not match data.length and colors.length');
			if ($chk(this.options.legends) && this.options.legends.length != this.options.data.length) alert('error: not match data.length and legends.length');
		},
		setLegendPosition: function(pos) { this.options.legendPosition = pos; },
		setColors: function(colors) { this.options.colors = colors; },
		setLegends: function(legends) { this.options.legends = legends; this.legends = true; },
		onAddParameter: function(params) {
			this.parent(params);
			if (this.hasLegend) params.include('chdlp', this.options.legendPosition);
			// from dataSet
			if ($chk(this.dataSet.getColors())) params.include('chco', this.dataSet.getColors().join(','));
			if ($chk(this.dataSet.getLegends())) params.include('chdl', this.dataSet.getLegends().join('|'));
			// from options
			if ($chk(this.options.colors)) params.include('chco', this.options.colors.join(','));
			if ($chk(this.options.legends)) params.include('chdl', this.options.legends.join('|'));
		}
	});
	/**
	* class: VennDiagrams
	*/
	gun.gchart.Venn = new Class({
		Extends: gun.gchart.Legends,
		options: {
			type: 'v',
			encode: gun.gchart.ENCODE_TEXT
		},
		initialize: function(options) {
			this.parent(options);
			if ($chk(this.options.data)) this.addData(this.options.data);
		},
		checkDataMatch: function() { }
	});
	/**
	* class: Bar Basic
	*/
	gun.gchart.Bar = new Class({
		Extends: gun.gchart.Legends,
		hasLegend: false,
		options: {
			type: 'bhg',
			width: 10,
			barSpace: 5,
			groupSpace: 15,
			zeroLine: [0.0]
		},
		initialize: function(options) {
			this.parent(options);
			if ($chk(this.options.data)) {
				for (var i = 0; i < this.options.data.length; i++) {
					this.addData(this.options.data[i], ($chk(this.options.colors) && $chk(this.options.colors[i])) ? this.options.colors[i] : null, ($chk(this.options.legends) && $chk(this.options.legends[i])) ? this.options.legends[i] : null);
				}
			}
		},
		setWidth: function(width) { this.options.width = width; },
		setBarSpace: function(space) { this.options.barSpace = space; },
		setGroupSpace: function(space) { this.options.groupSpace = space; },
		setZeroLine: function(lines) { this.options.zeroLine = lines; },
		onAddParameter: function(params) {
			this.parent(params);
			params.include('chbh', this.options.width + ',' + this.options.barSpace + ',' + this.options.groupSpace);
			if ($type(this.options.zeroLine) == 'number') {
				params.include('chp', this.options.zeroLine);
			} else if ($type(this.options.zeroLine) == 'array') {
				params.include('chp', this.options.zeroLine.join(','));
			}
		},
		addData: function(data, color, legend) {
			if ($chk(legend)) this.hasLegend = true;
			if ($type(data[0]) == 'array') {
				for (var i = 0; i < data.length; i++) {
					this.dataSet.addData(new gun.gchart.Data(data[i],($type(color)=='array' && $chk(color[i])) ? color[i] : null, ($type(legend)=='array' && $chk(legend[i])) ? legend[i] : legend) );
				}
			} else if ($type(data[0]) == 'object') {
				for (var i = 0; i < data.length; i++) {
					if ($chk(data[i].legend)) this.hasLegend = true;
					this.dataSet.addData(new gun.gchart.Data(data[i].data, $pick(data[i].color, null), $pick(data[i].legend, null)));
				}
			} else {
				this.dataSet.addData(new gun.gchart.Data(data, color, legend));
			}
		}
	});
	/**
	* class: Bar Horizontal
	*/
	gun.gchart.Bar.Horizontal = new Class({Extends: gun.gchart.Bar,options: {type: 'bhg'}});
	/**
	* class: Bar Vertical
	*/
	gun.gchart.Bar.Vertical = new Class({Extends: gun.gchart.Bar,options: {type: 'bvg'}});

}